Skip to content

[中文版本](/posts/c教程/zh-CN/07-贴近系统/069-Error Handling and Return Codes)

Every C program, from the tiniest script to the Linux kernel itself, relies on error codes and return values to communicate success or failure. Unlike higher-level languages, C gives you no exceptions, only clear, explicit status codes anderrno.

Mastering these patterns will make your programs robust, predictable, and professional.

Step 1. Exit Codes and main() ​

Every process returns an integer exit code to the operating system. Conventionally:

  • 0→ success
  • nonzero → failure or specific error
#include <stdio.h>
int main(void) {
    printf("Everything OK!\n");
    return 0; // exit success
}

Check it in your shell:

./program
echo $?

Output:

Everything OK!
0

If you return a nonzero value:

return 1;

then$? will be1, meaning failure.

Step 2. Using EXIT_SUCCESS and EXIT_FAILURE ​

Instead of hardcoded numbers, use standard macros from<stdlib.h>:

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    printf("Failed to open file.\n");
    return EXIT_FAILURE;
}

These improve portability and readability.

Step 3. The Global errno ​

When a library or system call fails, it usually sets a global variable namederrno. It’s declared in<errno.h>.

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
    int fd = open("nonexistent.txt", O_RDONLY);
    if (fd == -1) {
        printf("Error opening file: %s\n", strerror(errno));
    }
}

Output:

Error opening file: No such file or directory

errno stores an integer code, butstrerror() converts it into a readable message.

Step 4. Common errno Values ​

CodeMacroMeaning
2ENOENTNo such file or directory
13EACCESPermission denied
12ENOMEMOut of memory
22EINVALInvalid argument
9EBADFBad file descriptor
11EAGAINResource temporarily unavailable

You can check them directly:

if (errno == EACCES) { ... }

Step 5. The perror() Function ​

A simpler way to print error messages isperror(), it automatically uses the currenterrno.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
    int fd = open("nothing.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
    }
}

Output:

open: No such file or directory

Step 6. Returning Meaningful Codes ​

Good C programs translate internal errors into meaningful exit codes.

Example: file copy program

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s src dest\n", argv[0]);
        return 1;
    }
    FILE *src = fopen(argv[1], "r");
    if (!src) {
        perror("fopen src");
        return 2;
    }
    FILE *dst = fopen(argv[2], "w");
    if (!dst) {
        perror("fopen dst");
        fclose(src);
        return 3;
    }
    fclose(src);
    fclose(dst);
    return 0;
}

Now each exit code represents a specific type of failure.

Step 7. Resetting and Checking errno ​

Some system calls seterrno only when they fail. So always reset it before use if you plan to inspect it later:

#include <errno.h>
errno = 0;
if (some_syscall() == -1) {
    perror("syscall failed");
}

Step 8. Custom Error Handling Helpers ​

You can create your own function to simplify error handling.

#include <stdio.h>
#include <stdlib.h>
void die(const char *msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}
int main(void) {
    FILE *f = fopen("no.txt", "r");
    if (!f) die("fopen");
}

Output:

fopen: No such file or directory

This pattern appears throughout Unix utilities.

Step 9. Handling Partial Failures ​

Not all errors should abort your program. Sometimes you should log, retry, or ignore.

FILE *f = fopen("optional.conf", "r");
if (!f) {
    fprintf(stderr, "Warning: config file missing, using defaults.\n");
}

This kind of graceful degradation is good design.

Step 10. Tiny Code: Safe File Reader ​

#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
    FILE *f = fopen("data.txt", "r");
    if (!f) {
        fprintf(stderr, "Error: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    char buf[64];
    while (fgets(buf, sizeof(buf), f))
        printf("%s", buf);
    fclose(f);
    return EXIT_SUCCESS;
}

Output (if missing file):

Error: No such file or directory

Why It Matters ​

Error handling separates toy programs from real software:

  • Every syscall can fail, be prepared.
  • Always check return values.
  • Always report why it failed.

By convention:

  • Return0 on success.
  • Return nonzero for recoverable or fatal errors.
  • Print messages tostderr, notstdout.

Try It Yourself ​

  1. Open a nonexistent file and print the fullerrno value.
  2. Build a small utility that returns specific codes for specific problems.
  3. Useperror() vsstrerror() and compare their outputs.
  4. Add adie() helper to your previous exercises.
  5. Write a safe wrapper that retries system calls whenerrno == EAGAIN.

Next, you’ll put all of this together in Practice 70: Building a Mini Shell in C, where you’ll handle processes, pipes, and signals to create your own working Unix shell prototype.

本站总访问 — 次